﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using Microsoft.DirectX.Direct3D;
using SimulationPresenter.Presentation.Primitives;
using SimulationPresenter.Theater;
using Matrix = Microsoft.DirectX.Matrix;

namespace SimulationPresenter.Presentation.Helpers
{
    internal static class IndexedVerticesDrawer
    {
        internal static void Draw(IPresentationBase presentation, Matrix worldTransformation, Device device)
        {
            if (!presentation.Visible)
            {
                return;
            }

            if (presentation is IPresentationPrimitive)
            {
                Draw(presentation as IPresentationPrimitive, worldTransformation, device);
            }
            else
            {
                throw new NotSupportedException("Specified Presentation type is not supported at IndexedVerticesDrawer");
            }
        }
        #region Draw backing-up methods
        /// <summary>
        /// Draws given element by its indices and vertices to given device
        /// </summary>
        /// <param name="presentation"></param>
        /// <param name="worldTransformation"></param>
        /// <param name="device"></param>
        private static void Draw(IPresentationPrimitive presentation, Matrix worldTransformation, Device device)
        {
            if (presentation.Indices.Length % 3 != 0)
            {
                throw new InvalidOperationException(
                    "Only triaglebased elements are allowed. Indices list should be divideable by 3.");
            }
            //Todo: Check device state if scene is started

            VertexBuffer vertexBuffer = null;
            int verticiesCount = 0;

            switch (presentation.CustomVertexFormat)
            {
                case VertexFormats.PositionNormal | VertexFormats.Texture1:
                    var positionNormalTextured = presentation.GetVertices<CustomVertex.PositionNormalTextured>(); ;
                    vertexBuffer = positionNormalTextured.GetVertexBuffer(device);
                    verticiesCount = positionNormalTextured.Length;
                    break;
                case VertexFormats.PositionNormal | VertexFormats.Diffuse:
                    var positionNormalColored = presentation.GetVertices<CustomVertex.PositionNormalColored>();
                    vertexBuffer = positionNormalColored.GetVertexBuffer(device);
                    verticiesCount = positionNormalColored.Length;
                    break;
                case VertexFormats.Position | VertexFormats.Diffuse:
                    var positionColored = presentation.GetVertices<CustomVertex.PositionColored>();
                    vertexBuffer = positionColored.GetVertexBuffer(device);
                    verticiesCount = positionColored.Length;
                    break;
                default:
                    break;
            }

            var indexBuffer = new IndexBuffer(typeof(short),
                                               presentation.Indices.Length,
                                                device,
                                                Usage.WriteOnly,
                                                Pool.Default);

            indexBuffer.SetData(presentation.Indices, 0, LockFlags.None);

            Draw(vertexBuffer,
                    indexBuffer,
                    verticiesCount,
                    presentation.Indices.Length,
                    presentation.Texture,
                    presentation.Material,
                    presentation.UseScale ? presentation.scale : 1.0f,
                    worldTransformation,
                    device);
        }

        private static void Draw(VertexBuffer vertexBuffer, IndexBuffer indexBuffer, 
                                    int verticiesCount, int indicesCount, 
                                    Texture texture, Material material,
                                    float scaling,
                                    Matrix worldTransformation, Device device)
        {
            var scale = Matrix.Identity;
            scale.Scale(scaling, scaling, scaling);

            //Apply transformation
            device.Transform.World = scale * worldTransformation;

            device.SetStreamSource(0, vertexBuffer, 0);
            device.Indices = indexBuffer;
            
            //Clear material and texture
            device.Material = material;
            device.SetTexture(0, texture);


            //Draw element
            device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                                            0,
                                            0,
                                            verticiesCount,
                                            0,
                                            indicesCount / 3); //We assume that only triangles are built
        } 
        #endregion

        /// <summary>
        /// Builds mesh based on vertices and indices array
        /// </summary>
        /// <typeparam name="TVertex"></typeparam>
        /// <param name="vertices"></param>
        /// <param name="indices"></param>
        /// <param name="device"></param>
        /// <returns></returns>
        internal static VertexBuffer GetVertexBuffer<TVertex>(this TVertex[] vertices, Device device)
        {
            #region Check and get Vertex Format
            var format = typeof(TVertex).GetField("Format");

            if (null == format)
            {
                throw new ArgumentException("Type of vertices array is not a CustomVertex. Element is null.");
            }

            var vertexFormat = format.GetValue(typeof(TVertex).MakeByRefType());
            if (!(vertexFormat is VertexFormats))
            {
                throw new ArgumentException("Type of vertices array is not a CustomVertex. Element can't be casted to VertexFormats");
            }
            #endregion

            var meshVertexFormat = (VertexFormats)vertexFormat;

            device.VertexFormat = meshVertexFormat;

            var vertexBuffer = new VertexBuffer(typeof(TVertex),
                                                    vertices.Length,
                                                    device,
                                                    Usage.Dynamic | Usage.WriteOnly,
                                                    meshVertexFormat,
                                                    Pool.SystemMemory);
            //TODO: OUT OF MEMORY ERROR!

            vertexBuffer.SetData(vertices, 0, LockFlags.None);

            return vertexBuffer;
        } 

        private static readonly Material defaultMaterial = new Material()
                                                               {
                                                                   Ambient = Color.White,
                                                                   Diffuse = Color.White,
                                                                   Specular = Color.White
                                                               };

    }
}
